<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue';
import { Pt } from './utils/pt';
import { ptDistSq } from './utils/ptDist';
import { ptAzi } from './utils/ptAzi';
import cc from 'color-convert'
import { HEX } from 'color-convert/conversions';

const cvsW = 400
const cvsH = 400
const cvsClientW = 200
const cvsClientH = 200
const cvs = ref<HTMLCanvasElement>()
const cursorCvs = ref<HTMLCanvasElement>()
let ctx:CanvasRenderingContext2D
let cursorCtx:CanvasRenderingContext2D
const centerPt:Pt = {x:199, y:199}
const ringOuterRadius = 190
const ringInnerRadius = 150
const ringMiddleRadius = (ringOuterRadius+ringInnerRadius)/2
const ringDistSqUpper = ringOuterRadius**2
const ringDistSqLower = ringInnerRadius**2
const squareSide = 200
const squareLower = (cvsW-squareSide)/2
const squareUpper = squareLower+squareSide
const cursorRadius = 16
const cursorCenterRadius = 3

const hue = ref(0)
const sat = ref(100)
const val = ref(100)
const props = defineProps<{initialHex?:string}>()
let imgData:ImageData
function init(){
    const context = cvs.value?.getContext('2d')
    const cursorContext = cursorCvs.value?.getContext('2d')
    if(!context || !cursorContext)
        throw Error('canvas context getting error')
    if(props.initialHex){
        const hsv = cc.hex.hsv(props.initialHex)
        hue.value = hsv[0]
        sat.value = hsv[1]
        val.value = hsv[2]
    }
    ctx = context
    cursorCtx = cursorContext
    imgData = ctx.getImageData(0, 0, cvsW, cvsH)
    drawRing()
    drawSquare()
    drawCursors()
    ctx.putImageData(imgData, 0, 0)
}
function drawRing(){
    for(let x=0; x<cvsW; x++){
        for(let y=0; y<cvsH; y++){
            const pt = {x, y}
            if(onRing(pt)){
                const dataIdx = pt.y*cvsW*4 + pt.x*4
                const hueHere = ptToHue(pt)
                const c = cc.hsv.rgb([hueHere, 100, 100])
                imgData.data[dataIdx] = c[0]
                imgData.data[dataIdx+1] = c[1]
                imgData.data[dataIdx+2] = c[2]
                imgData.data[dataIdx+3] = 255
            }
        }
    }
}
function ptToHue(pt:Pt){
    const azi = ptAzi(pt, centerPt)+Math.PI
    const ratio = azi/(2*Math.PI)
    const hueHere = 360 * ratio
    return hueHere
}
function onRing(pt:Pt){
    const distToCenterSq = ptDistSq(centerPt, pt)
    return distToCenterSq>ringDistSqLower && distToCenterSq<ringDistSqUpper
}

function drawSquare(){
    for(let x=0; x<cvsW; x++){
        for(let y=0; y<cvsH; y++){
            const pt = {x, y}
            if(onSquare(pt)){
                const dataIdx = pt.y*cvsW*4 + pt.x*4
                const {s, v} = ptToSV(pt)
                const c = cc.hsv.rgb([hue.value, s, v])
                imgData.data[dataIdx] = c[0]
                imgData.data[dataIdx+1] = c[1]
                imgData.data[dataIdx+2] = c[2]
                imgData.data[dataIdx+3] = 255
            }
        }
    }
}
function ptToSV(pt:Pt){
    const x = pt.x - squareLower
    const y = pt.y - squareLower
    let xRatio = x/squareSide
    let yRatio = 1-y/squareSide
    if(xRatio>1)
        xRatio = 1
    else if(xRatio<0)
        xRatio = 0
    if(yRatio>1)
        yRatio = 1
    else if(yRatio<0)
        yRatio = 0
    return {s:100*xRatio, v:100*yRatio}
}
function onSquare(pt:Pt){
    if(pt.x < squareLower)
        return false
    if(pt.x > squareUpper)
        return false
    if(pt.y < squareLower)
        return false
    if(pt.y > squareUpper)
        return false
    return true
}

let processing = false
let doingThing:'none'|'ring'|'square' = 'none'
let mousePressing = false
function cvsEventHandler(e: MouseEvent|TouchEvent, isMouseMove?:boolean) {
    if(processing)
        return
    if(isMouseMove && !mousePressing)
        return
    processing = true
    const pt = evPt(e)
    if(doingThing==='none'){
        if(onSquare(pt))
            doingThing='square'
        else if(onRing(pt))
            doingThing='ring'
    }
    if (doingThing==='ring') {
        e.preventDefault()
        hue.value = ptToHue(pt)
        drawSquare()
        ctx.putImageData(imgData, 0, 0)
    }
    if (doingThing==='square'){
        e.preventDefault()
        const sv = ptToSV(pt)
        sat.value = sv.s
        val.value = sv.v
    }
    if(doingThing!=='none')
        drawCursors(true)
    processing = false
}
function cvsLeaveEventHandler(){
    doingThing = 'none'
    mousePressing = false
}
function evPt(e:MouseEvent|TouchEvent):Pt{
    let x:number,y:number
    if(!cvs.value)
        return {x:-1, y:-1}
    if('touches' in e){
        const rect = cvs.value.getBoundingClientRect()
        x = e.touches[0].clientX - rect.left
        y = e.touches[0].clientY - rect.top
    }else{
        x = e.offsetX
        y = e.offsetY
    }
    x = x * cvsW/cvsClientW
    y = y * cvsW/cvsClientW
    return {x,y}
}
function drawCursors(emit?:boolean){
    if(emit){
        const currentColor = cc.hsv.hex([hue.value, sat.value, val.value])
        emits('changed', currentColor)
    }

    const angle = (hue.value/360 * 2 + 1)*Math.PI
    const rx = Math.cos(angle) * ringMiddleRadius + cvsW/2
    const ry = Math.sin(angle) * ringMiddleRadius + cvsH/2
    const sx = sat.value/100 * squareSide + squareLower
    const sy = (1-val.value/100) * squareSide + squareLower
    cursorCtx.reset()
    cursorCtx.lineWidth = 4
    cursorCtx.strokeStyle = 'black'
    cursorCtx.beginPath()
    cursorCtx.arc(rx, ry, cursorRadius-2, 0, 2*Math.PI)
    cursorCtx.stroke()
    cursorCtx.beginPath()
    cursorCtx.arc(rx, ry, cursorCenterRadius, 0, 2*Math.PI)
    cursorCtx.fill()

    const squareCursorStyle = val.value > 50 ? 'black' : '#ddd'
    cursorCtx.strokeStyle = squareCursorStyle
    cursorCtx.fillStyle = squareCursorStyle
    cursorCtx.beginPath()
    cursorCtx.arc(sx, sy, cursorRadius-2, 0, 2*Math.PI)
    cursorCtx.stroke()
    cursorCtx.beginPath()
    cursorCtx.arc(sx, sy, cursorCenterRadius, 0, 2*Math.PI)
    cursorCtx.fill()
}
function enforceTo(hex:HEX){
    const hsv = cc.hex.hsv(hex)
    hue.value = hsv[0]
    sat.value = hsv[1]
    val.value = hsv[2]
    drawSquare()
    ctx.putImageData(imgData, 0, 0)
    drawCursors()
}

const emits = defineEmits<{
    (e:'changed', hex:HEX):void
}>()
defineExpose({enforceTo})

onMounted(()=>{
    init()
    window.addEventListener('touchend', cvsLeaveEventHandler)
    window.addEventListener('mouseup', cvsLeaveEventHandler)
})
onUnmounted(()=>{
    window.removeEventListener('touchend', cvsLeaveEventHandler)
    window.removeEventListener('mouseup', cvsLeaveEventHandler)
})
</script>

<template>
<div class="acpRingBody" :style="{width:cvsClientW+'px', height:cvsClientH+'px'}"
        @touchstart="cvsEventHandler" @touchmove="cvsEventHandler"
        @touchend="cvsLeaveEventHandler"
        @mousemove="e=>cvsEventHandler(e, true)"
        @mousedown="e=>{cvsEventHandler(e);mousePressing = true}">
    <canvas :width="cvsW" :height="cvsH" ref="cvs"></canvas>
    <canvas :width="cvsW" :height="cvsH" ref="cursorCvs"></canvas>
</div>
</template>

<style scoped>
.acpRingBody{
    position: relative;
}
canvas{
    position: absolute;
    inset: 0px;
    width: 100%;
    height: 100%;
}
</style>